/*SPI routines*/

/*Usart header file*/
#include "spi.h"

/*Global Variable definition*/
tSpiPort spiPort;

/*Constant definition*/

unsigned portCHAR ucGetCurrentSpeed(int cs)
{
    int res = *(AT91C_BASE_SPI->SPI_CSR + cs) >> 8;
return (unsigned portCHAR) res;
}

void spiHandleCS(eCSDevice device, unsigned portCHAR status)
{
    unsigned portLONG ulPeriph;
    switch (device)
    {
        case ChipSelect0: ulPeriph = SPI_IO_CS0; break;
        case ChipSelect1: ulPeriph = SPI_IO_CS1; break;
        case ChipSelect2: ulPeriph = SPI_IO_CS2; break;
        case ChipSelect3: ulPeriph = SPI_IO_CS3; break;
        default: return;
    }
    
    if (status)
    {
        AT91F_PIO_ClearOutput(AT91C_BASE_PIOA, ulPeriph);
    }
    else
    {
        AT91F_PIO_SetOutput(AT91C_BASE_PIOA, ulPeriph); 
    }
}

eSpiInitError eSpiInit(eCSDevice device)
{
    unsigned portLONG ulPeriph;
    
    /*Enable SPI PIO (SI, SO, SCK)*/
    AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA, (unsigned portLONG) AT91C_PA12_MISO | (unsigned portLONG) AT91C_PA13_MOSI | (unsigned portLONG) AT91C_PA14_SPCK, 0);

    /*Manually Set the Chip Select*/
    switch (device)
    {
        case ChipSelect0: ulPeriph = SPI_IO_CS0; break;
        case ChipSelect1: ulPeriph = SPI_IO_CS1; break;
        case ChipSelect2: ulPeriph = SPI_IO_CS2; break;
        case ChipSelect3: ulPeriph = SPI_IO_CS3; break;
        default: return spiInitMaxCs;
    }

    AT91F_PMC_EnablePeriphClock(AT91C_BASE_PMC, 1 << AT91C_ID_PIOA); //Enable PIO
    AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, ulPeriph); //Set Output
    AT91F_PIO_SetOutput(AT91C_BASE_PIOA, ulPeriph); //Set High

    /*Enable SPI Clocks*/
    AT91F_PMC_EnablePeriphClock(AT91C_BASE_PMC, 1 << AT91C_ID_SPI);

    /*Disable SPI while configuring*/
    AT91F_SPI_Disable(AT91C_BASE_SPI);
        
    /*Disable PDC for SPI*/
    AT91F_PDC_DisableRx(AT91C_BASE_PDC_SPI);
    AT91F_PDC_DisableTx(AT91C_BASE_PDC_SPI);

    /*Initialize PDC TX and RX*/
    AT91F_PDC_SetTx(AT91C_BASE_PDC_SPI, 0, 0);
    AT91F_PDC_SetNextTx(AT91C_BASE_PDC_SPI, 0, 0);
    AT91F_PDC_SetRx(AT91C_BASE_PDC_SPI, 0, 0);
	AT91F_PDC_SetNextRx(AT91C_BASE_PDC_SPI, 0, 0);

    /*Enable PDC for SPI*/
    AT91F_PDC_EnableRx(AT91C_BASE_PDC_SPI);
    AT91F_PDC_EnableTx(AT91C_BASE_PDC_SPI);

    /*Reset the module, necessarily 2 for the SPI state machine*/
    AT91F_SPI_Reset(AT91C_BASE_SPI); AT91F_SPI_Reset(AT91C_BASE_SPI);

    /*Enable SPI module*/
    AT91F_SPI_Enable(AT91C_BASE_SPI);
    
    /*Configure Master mode comunication*/
    AT91F_SPI_CfgMode(AT91C_BASE_SPI, AT91C_SPI_MSTR | AT91C_SPI_MODFDIS);

    /*Configure and enable interrupts*/
    AT91F_SPI_DisableIt(AT91C_BASE_SPI, AT91C_SPI_TXEMPTY | AT91C_SPI_RXBUFF);
    AT91F_AIC_ConfigureIt(AT91C_BASE_AIC, AT91C_ID_SPI, AT91C_AIC_PRIOR_LOWEST + 1, AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, ( void (*)(void) ) vSpiISREntry);
    AT91F_AIC_EnableIt(AT91C_BASE_AIC, AT91C_ID_SPI); 

    /*Create and initialize semaphores*/
    if (!spiPort.dev_1_Enabled && !spiPort.dev_2_Enabled && !spiPort.dev_3_Enabled && !spiPort.dev_4_Enabled)
    {
        vSemaphoreCreateBinary(spiPort.mutex.xSemaphore);
        vSemaphoreCreateBinary(spiPort.event.xSemaphore);
        spiPort.mutex.xBlockTime = INITIAL_OS_BLOCK_TIME_MAX;
    }
     
    /*Mark module as enabled, store config data*/
    switch (device)
    {
        case ChipSelect0: spiPort.dev_1_Enabled = 1; break;
        case ChipSelect1: spiPort.dev_2_Enabled = 1; break;
        case ChipSelect2: spiPort.dev_3_Enabled = 1; break;
        case ChipSelect3: spiPort.dev_4_Enabled = 1; break;
        default: return spiInitMaxCs;
    }

    return   spiInitSuccess;
}

void vSpiClose(eCSDevice device)
{
    unsigned portLONG ulPeriph;
    
    /*Disable SPI peripherial pins*/
    switch (device)
    {
        case ChipSelect0: ulPeriph = SPI_IO_CS0; spiPort.dev_1_Enabled = 0; break;
        case ChipSelect1: ulPeriph = SPI_IO_CS1; spiPort.dev_2_Enabled = 0; break;
        case ChipSelect2: ulPeriph = SPI_IO_CS2; spiPort.dev_3_Enabled = 0; break;
        case ChipSelect3: ulPeriph = SPI_IO_CS3; spiPort.dev_4_Enabled = 0; break;
        default: return;
    }
    AT91F_PIO_CfgInput(AT91C_BASE_PIOA, ulPeriph);

    if (!spiPort.dev_1_Enabled && !spiPort.dev_2_Enabled && !spiPort.dev_3_Enabled && !spiPort.dev_4_Enabled)
    {
        AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, (unsigned portLONG) AT91C_PA12_MISO | (unsigned portLONG) AT91C_PA13_MOSI | (unsigned portLONG) AT91C_PA14_SPCK);
        
        /*Disable PDC for SPI*/
        AT91F_PDC_DisableRx(AT91C_BASE_PDC_SPI);
        AT91F_PDC_DisableTx(AT91C_BASE_PDC_SPI);    
        
        /*Disable SPI module*/
        AT91F_SPI_Disable(AT91C_BASE_SPI);
    
        /*Disable SPI Clocks*/
        AT91F_PMC_DisablePeriphClock(AT91C_BASE_PMC, 1 << AT91C_ID_SPI);

        /*Disable interrupts*/
        AT91F_SPI_DisableIt(AT91C_BASE_SPI, AT91C_SPI_TXEMPTY | AT91C_SPI_RXBUFF);
        AT91F_AIC_DisableIt(AT91C_BASE_AIC, AT91C_ID_SPI);

        /*Release Semaphores*/
        vQueueDelete(spiPort.mutex.xSemaphore);
        vQueueDelete(spiPort.event.xSemaphore);
    }    
}

unsigned portCHAR cSpiReadWrite(pSpiRWdata pData)
{
unsigned portCHAR errCode;   
unsigned portCHAR divider = pData->clockDivider;

    switch (pData->device)
    {
        case ChipSelect0: if (!spiPort.dev_1_Enabled) {return ERR_DEV_UNINIT;}; break;
        case ChipSelect1: if (!spiPort.dev_2_Enabled) {return ERR_DEV_UNINIT;}; break;
        case ChipSelect2: if (!spiPort.dev_3_Enabled) {return ERR_DEV_UNINIT;}; break;
        case ChipSelect3: if (!spiPort.dev_4_Enabled) {return ERR_DEV_UNINIT;}; break;
        default: return ERR_OUT_OF_BOUNDS;
    }

    if (xSemaphoreTake(spiPort.mutex.xSemaphore, (portTickType) spiPort.mutex.xBlockTime ) != pdTRUE)
    {
        return ERR_SEM_TIMEOUT;
    }

    /*Set baud rate for SPI*/
    if (divider < SPI_MIN_DIVIDER) {divider = SPI_MIN_DIVIDER;}
    if (divider > 1) {divider &= 0xFE;}
    
    /*Configure for the clock phase, bits per transfer, baud rate*/
    AT91F_SPI_CfgCs(AT91C_BASE_SPI, pData->device, AT91C_SPI_CPOL | AT91C_SPI_BITS_8 | ((unsigned portLONG) divider << 8));

    /*Disable PDC while configuring or on the fly*/
    #if SPI_SENDRCV_DIS_W_SETTING == 1
    AT91F_PDC_DisableTx(AT91C_BASE_PDC_SPI);
    AT91F_PDC_DisableRx(AT91C_BASE_PDC_SPI);
    #endif

    /*Take blocking semaphore*/
    xSemaphoreTake(spiPort.event.xSemaphore, 0);
    
    /*Load wait block time*/
    spiPort.event.xBlockTime = pData->xBlockTime;

    /*Has bytes to write*/
    if (pData->sizeW == 0)
    {
        AT91F_PDC_SetTx(AT91C_BASE_PDC_SPI, (char *) pData->dataR, pData->sizeR);
    }
    else
    {
        AT91F_PDC_SetTx(AT91C_BASE_PDC_SPI, (char *) pData->dataW, pData->sizeW);
    }  
    
    /*Has bytes to read*/
    if (pData->sizeR > 0)
    {
        AT91F_PDC_SetRx(AT91C_BASE_PDC_SPI, (char *) pData->dataR, pData->sizeR);
        /*When TX & Rx only RX interrupt is necessary*/
        AT91F_SPI_EnableIt(AT91C_BASE_SPI, AT91C_SPI_RXBUFF);
      
        #if SPI_SENDRCV_DIS_W_SETTING == 1
        AT91F_PDC_EnableRx(AT91C_BASE_PDC_SPI);
        #endif
    }
    else
    {
        /*Enable TX interrupt*/
        AT91F_SPI_EnableIt(AT91C_BASE_SPI, AT91C_SPI_TXEMPTY);   
    }
    
    /*Enable Transmission*/
    #if SPI_SENDRCV_DIS_W_SETTING == 1
    AT91F_PDC_EnableTx(AT91C_BASE_PDC_SPI);
    #endif

    /*Block to wait results*/
    if (xSemaphoreTake(spiPort.event.xSemaphore, (portTickType) spiPort.event.xBlockTime ) == pdTRUE)
    {
        /*Empty receiver-register*/	
        if (pData->sizeR > 0) {AT91F_SPI_GetChar(AT91C_BASE_SPI);}
        errCode = SUCCESS;
    }
    else { errCode = ERR_TASK_TIMEOUT; }

    /*Release mutex*/
    xSemaphoreGive(spiPort.mutex.xSemaphore);

    return errCode;
}

void vSpiISR(void)
{    
    portBASE_TYPE xTaskWokenBySPI = pdFALSE;
    unsigned portLONG ulStatus;

    /* Original cause of the interruption */
    ulStatus = AT91C_BASE_SPI->SPI_SR &= AT91C_BASE_SPI->SPI_IMR;

    /*Is a transmit interrupt*/
    if (ulStatus & (AT91C_SPI_RXBUFF | AT91C_SPI_TXEMPTY))
    {
        AT91F_SPI_DisableIt(AT91C_BASE_SPI, AT91C_SPI_TXEMPTY | AT91C_SPI_RXBUFF);
        /*Signal semaphore*/
        xSemaphoreGiveFromISR(spiPort.event.xSemaphore, &xTaskWokenBySPI);
    }
    
    /* End the interrupt in the AIC. */
    AT91C_BASE_AIC->AIC_EOICR = 0;
    
    /* If a task was woken by either a character being received or a character 
    being transmitted then we may need to switch to another task. */
    portEND_SWITCHING_ISR(xTaskWokenBySPI); 
}
